<template>
  <div class="e-table-form">
    <template v-if="$slots.default">
      <el-form
        ref="form"
        class="s-form"
        label-position="right"
        :label-width="props.labelWidth"
        :model="props.model"
        :disabled="loading"
        :inline="props.formInline"
        @submit.prevent="initSearch"
      >
        <div
          class="form-rows"
          :class="{
            'rows-height': props.more,
            'rows-open': moreState
          }"
        >
          <slot />
          <el-form-item v-if="props.formInline && !props.more" label-width="0" class="form-inline">
            <el-button :icon="Search" type="primary" native-type="submit">
              搜索
            </el-button>
            <el-button :icon="RefreshRight" type="info" @click="reSetForm">
              重置
            </el-button>
          </el-form-item>
        </div>
        <el-row v-if="!props.formInline || props.more">
          <table-form-item align="right" label-width="0" class="form-button">
            <el-button :icon="Search" type="primary" native-type="submit">
              搜索
            </el-button>
            <el-button :icon="RefreshRight" type="info" @click="reSetForm">
              重置
            </el-button>
            <el-button :icon="Filter" type="info" @click="moreState = !moreState" v-if="props.more">
              {{ moreState ? '收起' : '更多筛选' }}
            </el-button>
          </table-form-item>
        </el-row>
      </el-form>
    </template>
    <div class="e-table">
      <div class="e-tools" v-if="props.tools">
        <div class="tools-left">
          <slot name="toolsLeft" />
          <div class="table-header">
            {{ props.title || currentRoute.meta.title }}
          </div>
        </div>
        <div class="tools-right">
          <template v-if="$slots.dialogForm">
            <el-form
              ref="form"
              class="d-form"
              label-position="right"
              :model="props.model"
              :disabled="loading"
              inline
              @submit.prevent="initSearch"
            >
              <slot name="dialogForm" />
            </el-form>
          </template>
          <template v-if="$slots.dialogForm">
            <el-tooltip content="搜索" placement="top">
              <el-button :icon="Search" type="primary" @click="initSearch" />
            </el-tooltip>
            <el-tooltip content="重置" placement="top">
              <el-button :icon="RefreshRight" type="info" @click="reSetForm" />
            </el-tooltip>
          </template>
          <slot name="toolsRight" />
          <el-button
            v-if="isExpand"
            type="info"
            :icon="Operation"
            @click="expandTable"
          >
            展开/折叠
          </el-button>
          <el-radio-group v-model="listType" class="table-radio" @change="radioChange" v-if="props.isCard">
            <el-tooltip content="表格布局" placement="top">
              <el-radio-button label="table">
                <SvgIcon font="biaodanzujian-biaoge" :size="14" />
              </el-radio-button>
            </el-tooltip>
            <el-tooltip content="卡片布局" placement="top">
              <el-radio-button label="card">
                <SvgIcon font="qiapian" :size="14" />
              </el-radio-button>
            </el-tooltip>
          </el-radio-group>
          <el-tooltip content="导出" placement="top">
            <el-button :icon="Download" type="info" @click="handleExportExcel" />
          </el-tooltip>
          <el-tooltip content="刷新" placement="top">
            <el-button :icon="Refresh" type="info" @click="initTable" />
          </el-tooltip>
          <el-popover :width="200" trigger="click">
            <template #reference>
              <div class="trgger-content">
                <el-tooltip content="显藏列" placement="top">
                  <el-button :icon="Grid" type="info" />
                </el-tooltip>
              </div>
            </template>
            <div class="custom">
              <div class="custom-header">
                <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">
                  全部
                </el-checkbox>
                <p class="reset-sort" @click="resetSort">
                  重置排序
                </p>
              </div>
              <el-checkbox-group class="custom-data" v-model="checkGroup" @change="handleCheckedCitiesChange">
                <el-checkbox v-for="item in props.columns" :key="item.prop" :label="item.prop">
                  {{ item.label }}
                  <span
                    v-if="item.sortable"
                    class="caret-wrapper"
                    :class="item.prop === sortProp ? sortType : ''"
                    @click.prevent="sortable(item)"
                  >
                    <i class="sort-caret ascending" />
                    <i class="sort-caret descending" />
                  </span>
                </el-checkbox>
              </el-checkbox-group>
            </div>
          </el-popover>
        </div>
      </div>
      <div class="e-table-data" v-loading="loading">
        <el-table
          v-show="listType === 'table'"
          ref="table"
          :data="props.noChildren ? rawTableData : tableData"
          row-key="id"
          :select-on-indeterminate="false"
          :stripe="!isExpand"
          :height="props.height"
          :class="{highlight: props.highlight}"
          :highlight-current-row="props.highlight"
          :default-expand-all="props.defaultExpandAll"
          @select="select"
          @select-all="selectAll"
          @selection-change="selectionChange"
          @current-change="currentChange"
          @sort-change="sortChange"
        >
          <el-table-column v-if="props.selection" type="selection" align="center" width="50" />
          <el-table-column
            v-if="props.index"
            type="index"
            label="序号"
            align="center"
            width="60"
          >
            <template #default="{ $index }">
              {{ ($index + 1) + ((page.page - 1) * page.size) }}
            </template>
          </el-table-column>
          <el-table-column
            v-for="item of columnsData"
            :key="item.prop"
            :prop="item.prop"
            :label="item.label"
            :width="item.width"
            :sort-by="item.prop"
            :sortable="item.sortable"
            :align="item.align || 'center'"
            :header-align="item.align || 'center'"
            :show-overflow-tooltip="true"
          >
            <template #default="{ row }">
              <to-image v-if="item.img" style="width: 100px;height: 100px;margin: 0 auto;" :src="formatter(row, item)" />
              <component
                v-else-if="item.component"
                :is="item.component"
                v-bind="componentAttr(row, item)"
                v-on="componentEvent(row, item)"
              >
                {{ formatter(row, item) }}
              </component>
              <span v-else v-html="formatter(row, item)" />
            </template>
          </el-table-column>
          <el-table-column v-if="props.menus.length" label="操作" align="center" :width="menusWidth">
            <template #default="{ row, $index }">
              <div class="menu-group">
                <p
                  v-for="(menu, menuIndex) of getMenuList(row)"
                  :key="menuIndex"
                  :style="{color: getMenuColor(menu)}"
                  @click="emit('event', menu.event, row, $index)"
                >
                  {{ menu.name }}
                </p>
                <el-dropdown trigger="click" v-if="getMenuList(row, true).length">
                  <p class="el-dropdown-link">
                    <span>更多</span>
                    <el-icon>
                      <DArrowRight />
                    </el-icon>
                  </p>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item
                        v-for="(menu, menuIndex) of getMenuList(row, true)"
                        :key="menuIndex"
                        @click="emit('event', menu.event, row, $index)"
                      >
                        {{ menu.name }}
                      </el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </div>
            </template>
          </el-table-column>
        </el-table>
        <CardList
          v-if="props.isCard"
          v-show="listType === 'card'"
          :data="sortData"
          :menus="props.menus"
          :columns="columnsData"
          :default-table-text="props.defaultTableText"
          @event="cardEvent"
        />
      </div>
      <el-pagination
        v-if="props.page"
        class="e-pages"
        :total="page.totalElements"
        :page-size="page.size"
        :current-page="page.page"
        :page-sizes="[10, 20, 30, 40, 50, 100]"
        @update:page-size="handleSizeChange"
        @update:current-page="handleCurrentChange"
        background
        layout="prev, pager, next, total, sizes"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, reactive, computed, nextTick, watch } from 'vue'
import { DArrowRight, Grid, Refresh, Download, Search, RefreshRight, Filter, Operation } from '@element-plus/icons'
import type { IElForm, ITable } from '@/types/element-plus'
import type { TableColumnsProps, MenuProps } from './types/tableForm'
import { checkPermission, DealData, exportData } from '@/utils/utils'
import CardList from './CardList.vue'
import { orderBy } from 'element-plus/es/components/table/src/util'
import { useRouter } from 'vue-router'

interface TableFormPropsType {
  // 左侧自定义标题
  title?: string
  // 是否开启工具栏
  tools?: boolean
  // 是否自动执行load
  autoLoad?: boolean
  // 列数据
  columns?: TableColumnsProps[]
  // 菜单
  menus?: MenuProps[]
  // 菜单展示最大数量
  menuMaxNum?: number
  // 表单数据
  model?: {[key: string]: any}
  // 加载事件
  load?: (form: any) => Promise<any>
  // 开启更多选项
  more?: boolean
  // 是否默认显示更多
  defaultMore?: boolean
  // 开启多选
  selection?: boolean
  // 高亮选中
  highlight?: boolean
  // 开启序号
  index?: boolean
  // 表格数据显示默认值
  defaultTableText?: string
  // 表单内联
  formInline?: boolean
  // 表单展示行
  rows?: number
  // 表单行高
  rowHeight?: number
  // 表格高度
  height?: string | number
  // 表单label宽度
  labelWidth?: number | string
  // 是否开启翻页
  page?: boolean
  // 取消内部级联处理，用于后端处理好级联数据使用，内部会直接判定为是级联数据
  noChildren?: boolean
  // 默认展开菜单
  defaultExpandAll?: boolean
  // 是否开启卡片模式切换
  isCard?: boolean
  // 默认显示布局模式
  defaultType?: 'table' | 'card'
}

const router = useRouter()
const currentRoute = router.currentRoute
const props = withDefaults(defineProps<TableFormPropsType>(), {
  title: '',
  tools: true,
  autoLoad: true,
  columns: () => [],
  menus: () => [],
  menuMaxNum: 3,
  menuNum: 2,
  model: undefined,
  load: (form: any) => Promise.resolve(form),
  more: false,
  defaultMore: false,
  selection: false,
  highlight: false,
  index: true,
  defaultTableText: '',
  formInline: false,
  rows: 2,
  rowHeight: 36,
  height: '100%',
  labelWidth: 'auto',
  page: true,
  noChildren: false,
  defaultExpandAll: false,
  isCard: false,
  defaultType: 'table'
})

interface EmitsType {
  (e: 'update:menus', menus: MenuProps[]): void
  (e: 'event', event: MenuProps['event'], row: any, index?: number): void
  (e: 'select', row: any[]): void
  (e: 'current-change', row: any): void
}
const emit = defineEmits<EmitsType>()

// 表单相关
const form = ref<IElForm>()

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const rowHeight = computed(() => `${((props.rows * props.rowHeight) + (props.rows - 1) * 10)}px`)
const moreState = ref(props.defaultMore)
const reSetForm = () => {
  form.value?.resetFields()
  fetchData()
}
const initSearch = () => {
  page.page = 1
  fetchData()
}
// 动态组件参数
const componentAttr = (row: any, item: TableColumnsProps) => {
  if (typeof item.attr === 'function') {
    return item.attr(row) || {}
  }
  return item.attr || {}
}
// 动态组件事件
const componentEvent = (row: any, item: TableColumnsProps) => {
  if (typeof item.event === 'function') {
    return item.event(row) || {}
  }
  return item.event || {}
}
// 格式化内容
const formatter = (row: any, item: TableColumnsProps): string => {
  if (item.formatter) {
    return item.formatter(row, item) || props.defaultTableText
  }
  // 异步函数存在，调用
  item.asyncFormat && item.asyncFormat(row, item)
  // 存在img，直接返回prop
  if (item.img) return row[item.prop]
  return row[item.prop] || props.defaultTableText
}

// 操作相关
const listType = ref(props.defaultType)
const checkAll = ref()
const checkGroup = ref<string[]>([])
const isIndeterminate = ref(false)
const columnsData = computed(() => props.columns.filter(item => item.visible))
const handleCheckAllChange = (val: boolean) => {
  checkGroup.value = val ? props.columns.map(item => item.prop) : []
  isIndeterminate.value = false
  handleCheckedCitiesChange(checkGroup.value)
}
const handleCheckedCitiesChange = (val: string[]) => {
  const checkedCount = val.length
  checkAll.value = checkedCount === props.columns.length
  isIndeterminate.value = checkedCount > 0 && checkedCount < props.columns.length
  props.columns.forEach(item => {
    item.visible = false
    if (checkGroup.value.includes(item.prop)) {
      item.visible = true
    }
  })
}
const handleExportExcel = () => {
  exportData(props.columns, rawTableData.value)
}

const sortData = ref([])
const sortType = ref<'ascending' | 'descending' | null>(null)
const sortProp = ref('')
// radio变化，清空复选框，card模式暂不支持多选
const radioChange = () => {
  tableData.value.forEach((row: any) => {
    toggleSelection(row, false)
  })
  selectAll([])
}
// 自定义排序
const sortable = (row: TableColumnsProps) => {
  if (!sortType.value) {
    sortType.value = 'ascending'
    sortProp.value = row.prop
    setSortData()
  } else if (sortType.value === 'ascending') {
    sortType.value = 'descending'
    sortProp.value = row.prop
    setSortData()
  } else {
    resetSort()
  }
}
// 监听table sort事件
const sortChange = (sort: any) => {
  if (!sort.prop) return resetSort()
  sortType.value = sort.order
  sortProp.value = sort.prop
  setSortData(true)
}
// 设置排序
const setSortData = (tableSort: boolean = false) => {
  const list = (props.noChildren ? rawTableData.value : tableData.value)
  if (sortType.value) {
    !tableSort && table.value?.sort(sortProp.value, sortType.value)
    sortData.value = orderBy(list, sortProp.value, sortType.value, null, '')
  } else {
    sortData.value = list
  }
}
// 重置排序
const resetSort = () => {
  sortType.value = null
  sortProp.value = ''
  table.value?.clearSort()
  sortData.value = (props.noChildren ? rawTableData.value : tableData.value)
}

// 表格行菜单
const checkMenus = () => {
  const menus = props.menus.filter((item) => {
    if (item.role) {
      const roles = [item.role]
      const hasPermission = checkPermission(roles)
      if (!hasPermission) return false
    }
    return true
  })
  emit('update:menus', menus)
}
// 获取隐藏/显示按钮的列表
const getMenuList = computed(() => getMenuListFn)
const getMenuListFn = (row: any, isVisible: boolean = false) => {
  const menus = props.menus.filter((item) => {
    if (typeof item.visible === 'boolean') return !item.visible
    if (typeof item.visible === 'function') return !item.visible(row)
    return true
  })
  let visibleMenus: any = []
  if (menus.length > props.menuMaxNum) {
    visibleMenus = menus.splice(props.menuMaxNum - 1)
  }
  if (isVisible) return visibleMenus
  return menus
}
// 获取按钮颜色
const getMenuColor = (row: MenuProps) => {
  let color = row.color || ''
  if (row.type) {
    color = `var(--el-color-${row.type})`
  }
  return color
}
const menusWidth = computed(() => (props.menuMaxNum) * 60 + 20)

// 翻页相关
const page = reactive({
  page: 1,
  size: 20,
  totalElements: 20
})
const handleSizeChange = (size: number) => {
  page.page = 1
  page.size = size
  fetchData()
}
const handleCurrentChange = (val: number) => {
  page.page = val
  fetchData()
}

// 表格实例
const table = ref<ITable>()
// 是否存在树节点
const isExpand = ref(false)
// 展开折叠状态
const expandState = ref(props.defaultExpandAll)
// 展开折叠事件
const expandTable = () => {
  rawTableData.value.forEach(data => {
    table.value?.toggleRowExpansion(data, !expandState.value)
  })
  expandState.value = !expandState.value
}
// 数据相关
const loading = ref(false)
// 原始数据
const rawTableData = ref([])
// 尝试转换树结构
const tableData = ref([])
const fetchData = async () => {
  // 置空选项状态
  if (isExpand.value) {
    selectAll([])
  }
  loading.value = true
  try {
    const data = await props.load(Object.assign({}, props.model || {}, {
      page: page.page - 1,
      size: page.size
    }))
    loading.value = false
    if (data.content) {
      page.totalElements = data.totalElements
      rawTableData.value = data.content
      tableData.value = props.noChildren ? data.content : DealData(data.content)
      // 原始数据跟处理数据结果不一致为级联数据
      if (rawTableData.value.length !== tableData.value.length) {
        isExpand.value = true
      }
      if (props.noChildren) {
        isExpand.value = false
      }
      setSortData()
    } else {
      tableData.value = data || []
    }
  } catch (e) {
    loading.value = false
  }
}

// 复选事件
const toggleSelection = async (row: any, state: boolean) => {
  if (row) {
    await nextTick()
    table.value?.toggleRowSelection(row, state)
  }
}
// 递归设置选中状态
const setChildren = (children: any[], type: boolean) => {
  if (children && Array.isArray(children)) {
    children.forEach(row => {
      toggleSelection(row, type)
      if (row.children) {
        setChildren(row.children, type)
      }
    })
  }
}
// 选中父节点时，子节点一起选中取消
const select = (selection: any[], row: any) => {
  const checked = selection.some(item => row.id === item.id)
  setChildren(row.children, checked)
}
// 全选
const selectAll = (selection: any[]) => {
  const isSelectAll = selection.some(item => {
    const ids = tableData.value.map((table: any) => table.id)
    return ids.includes(item.id)
  })
  if (isSelectAll) {
    selection.forEach(item => {
      setChildren(item.children, true)
    })
  } else {
    tableData.value.forEach((table: any) => {
      setChildren(table.children, false)
    })
  }
}
// 复选框变化抛出事件
const selectionChange = (selection: any[]) => {
  emit('select', selection)
}
// 表格单选事件
const currentChange = (currentRow: any) => {
  emit('current-change', currentRow)
}
// 表格组件按钮事件
const cardEvent = (event: string, row: any, index: number) => {
  emit('event', event, row, index)
}

// 重置check状态
const initCheckGroup = () => {
  checkGroup.value = []
  props.columns.forEach((item) => {
    if (item.visible === undefined) {
      item.visible = true
    }
    item.visible && checkGroup.value.push(item.prop)
  })
  handleCheckedCitiesChange(checkGroup.value)
}

watch(() => [...props.columns], () => {
  initCheckGroup()
  nextTick(() => {
    table.value?.doLayout()
  })
})

// 初始化变量
const initTable = () => {
  initCheckGroup()
  if (props.autoLoad) fetchData()
  checkMenus()
}

initTable()

// 实例返回
defineExpose({
  rawTableData,
  tableData,
  sortData,
  fetchData
})
</script>

<style lang="scss" scoped>
.e-table-form {
  display: flex;
  flex-direction: column;
  height: 100%;
  :deep(.s-form) {
    position: relative;
    .form-rows {
      overflow: hidden;
      &:last-child {
        margin-bottom: $space-margin;
        border-bottom: 1px solid rgba(130, 145, 169, 0.25);
      }
      &.rows-height {
        max-height: v-bind(rowHeight);
        margin-bottom: $space-margin;
      }
      &.rows-open {
        max-height: inherit;
        margin-bottom: 0;
      }
      > .form-inline {
        width: auto !important;
        margin-right: 0 !important;
        float: right;
      }
      > .el-form-item {
        width: 200px;
      }
    }
    .form-button {
      text-align: right;
      float: right;
      margin-right: 0 !important;
      > .el-form-item__content {
        justify-content: flex-end;
      }
    }
    .el-form-item {
      margin-bottom: $space-margin;
      width: 100%;
    }
  }
  :deep(.d-form) {
    .el-form-item {
      width: 200px;
      margin-bottom: 0;
      &.form-inline {
        width: inherit;
      }
    }
  }
  .e-table {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    .e-tools {
      display: flex;
      justify-content: space-between;
      margin-bottom: $space-margin;
      > div {
        display: flex;
      }
      .tools-right {
        flex: 1;
        margin-left: 10px;
        overflow: hidden;
        justify-content: flex-end;
      }
      .table-header {
        position: relative;
        display: flex;
        align-items: center;
        color: var(--el-text-color-primary);
        padding-left: 20px;
        font-size: 18px;
        font-weight: 700;
        &::before {
          content: '';
          position: absolute;
          left: 0;
          width: 5px;
          top: 50%;
          transform: translate(0, -50%);
          height: 70%;
          border-radius: 0px 2px 2px 0px;
          background: linear-gradient(171deg, #0081FF 0%, #0081FF 0%, #22CCE2 101%, #22CCE2 101%);
        }
      }
      .trgger-content {
        display: inline-block;
        margin-left: $space-margin;
      }
      .table-radio {
        margin: 0 12px;
      }
    }
    .e-table-data {
      flex: 1;
      box-sizing: border-box;
      overflow: hidden;
      .menu-group {
        display: flex;
        justify-content: center;
        margin-top: $space-margin;
        p, .el-dropdown {
          text-align: center;
          line-height: inherit;
          color: var(--el-color-info);
          cursor: pointer;
          margin-right: 10px;
          &:hover {
            color: var(--el-color-info-light-3);
          }
          &:active {
            color: var(--el-color-info);
          }
          &:last-child {
            margin: 0;
          }
        }
        .el-dropdown-link {
          display: flex;
          align-items: center;
        }
        &:last-child {
          margin: 0;
        }
        .el-button {
          width: 100px;
        }
      }
      .highlight {
        :deep(tr.el-table__row) {
          cursor: pointer;
        }
      }
    }
    .e-pages {
      text-align: center;
      justify-content: center;
      margin-top: $space-margin;
      :deep(.el-pagination__total) {
        margin-left: 16px;
        margin-right: 0;
      }
    }
  }
}
:deep(.custom-header) {
  border-bottom: 1px solid var(--el-border-color-base);
  position: relative;
  .reset-sort {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--el-color-info);
    cursor: pointer;
    &:hover {
      color: var(--el-color-info-light-3);
    }
  }
}
:deep(.custom-data) {
  display: flex;
  flex-direction: column;
  .el-checkbox {
    margin-right: 30px;
  }
  .caret-wrapper {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    vertical-align: middle;
    cursor: pointer;
    position: absolute;
    width: 20px;
    right: -30px;
    top: 50%;
    transform: translateY(-50%);
    &.ascending .sort-caret.ascending {
      border-bottom-color: var(--el-color-primary);
    }
    &.descending .sort-caret.descending {
      border-top-color: var(--el-color-primary);
    }
    .sort-caret {
      display: inline-block;
      width: 0;
      height: 0;
      border: solid 5px transparent;
      &.ascending {
        border-bottom-color: var(--el-text-color-placeholder);
        margin-bottom: 3px;
      }
      &.descending {
        border-top-color: var(--el-text-color-placeholder);
      }
    }
  }
}

:deep(.el-table__inner-wrapper::before) {
  content: none;
}
</style>
